﻿using OnDash.Mathematics.Vector;

namespace OnDash.Mathematics.Geometry;

public struct BezierCurve
{
    private readonly List<Vector2> _points;
    public readonly float Parallel;

    public IList<Vector2> Points => _points;

    public BezierCurve(IEnumerable<Vector2> points)
    {
        if (points == null)
            throw new ArgumentNullException(nameof(points), "Must point to a valid list of Vector2 structures.");

        _points = new List<Vector2>(points);
        Parallel = 0f;
    }

    public BezierCurve(params Vector2[] points)
    {
        if (points == null)
            throw new ArgumentNullException(nameof(points), "Must point to a valid list of Vector2 structures.");

        _points = new List<Vector2>(points);
        Parallel = 0f;
    }

    public BezierCurve(float parallel, params Vector2[] points)
    {
        if (points == null)
            throw new ArgumentNullException(nameof(points), "Must point to a valid list of Vector2 structures.");

        _points = new List<Vector2>(points);
        Parallel = parallel;
    }

    public BezierCurve(float parallel, IEnumerable<Vector2> points)
    {
        if (points == null)
            throw new ArgumentNullException(nameof(points), "Must point to a valid list of Vector2 structures.");

        _points = new List<Vector2>(points);
        Parallel = parallel;
    }

    private static Vector2 CalculatePointOfDerivative(ICollection<Vector2> points, float t)
    {
        var r = new Vector2();
        var c = 1f - t;
        var i = 0;

        foreach (var pt in points)
        {
            var temp = MathHelper.BinomialCoefficient(points.Count - 2, i) * (MathHelper.Pow(t, i) *
                                                                              MathHelper.Pow(c,
                                                                                  points.Count - 2 - i));

            r.X += temp * pt.X;
            r.Y += temp * pt.Y;
            i++;
        }

        return r;
    }

    public static Vector2 CalculatePoint(IList<Vector2> points, float t, float parallel)
    {
        var r = new Vector2();
        var c = 1f - t;
        var i = 0;

        foreach (var pt in points)
        {
            var temp = MathHelper.BinomialCoefficient(points.Count - 1, i) * (MathHelper.Pow(t, i) *
                                                                              MathHelper.Pow(c,
                                                                                  (points.Count - 1) - i));

            r.X += temp * pt.X;
            r.Y += temp * pt.Y;
            i++;
        }

        if (parallel == 0f) return r;

        Vector2 perpendicular;

        if (t != 0f) perpendicular = r - CalculatePointOfDerivative(points, t);
        else perpendicular = points[1] - points[0];

        return r + Vector2.Normalize(perpendicular).PerpendicularRight * parallel;
    }

    public Vector2 CalculatePoint(float t) => CalculatePoint(_points, t, Parallel);

    public static Vector2 CalculatePoint(IList<Vector2> points, float t) => CalculatePoint(points, t, 0f);

    public float CalculateLength(float precision) =>
        CalculateLength(_points, precision, Parallel);

    public static float CalculateLength(IList<Vector2> points, float precision) =>
        CalculateLength(points, precision, 0f);

    public static float CalculateLength(IList<Vector2> points, float precision, float parallel)
    {
        var length = 0f;
        var old = CalculatePoint(points, 0f, parallel);

        for (var i = precision; i < (1f + precision); i += precision)
        {
            var n = CalculatePoint(points, i, parallel);
            length += (n - old).Length;
            old = n;
        }

        return length;
    }
}